import re
from datetime import datetime
import pywintypes
# import psutil

from qturboframe.lib.report.log import logger
from qturboframe.lib.ranorex.exceptions import CsClientError
from qturboframe.lib.utils.CustomError.custom_error import CustomError

class Control(object):
    '''
    控件基类
    '''

    def __init__(self, control):
        self.control = control

    def get_text(self):
        '''
        获取控件的文本值

        :Returns:
            返回控件文本值
        '''
        return self.control.GetText

    def set_control_id(self, id):
        '''
        设置控件的ID值

        :Args:
         - id: ID值
        '''
        self.control.SetControlId(id)

    def get_control_id(self):
        '''
        获取控件的ID值

        :Returns:
            返回控件的ID值
        '''
        return self.control.GetControlId

    def is_enable(self):
        '''
        判断控件是否使能

        :Returns:
            True表示使能，反之表示不使能
        '''
        return self.control.IsEnable

    def is_visible(self):
        '''
        判断控件是否可见

        :Returns:
            True表示可见，反之表示不可见
        '''
        return self.control.IsVisible

    def get_control_name(self):
        '''
        获取控件的类型

        :Returns:
            返回Ranorex的PreferredCapabilit中DisplayName属性
        '''
        return self.control.GetControlName

    def get_class(self):
        '''
        获取控件的class类型

        :Returns:
            返回Ranorex属性中的class值
        '''
        return self.control.GetClass

    def get_adapter(self):
        '''
        获取控件的Adapter对象

        :Returns:
            返回控件的Adapter对象
        '''
        return self.control.GetAdapter

    def right_click(self, x=0, y=0):
        '''
        控件右击，默认点击控件的正中间

        :Args:
         - x: X轴坐标，默认0
         - y: Y轴坐标，默认0
        '''
        self.control.RightClick_2(x, y)

    def click(self, x=0, y=0, move_time=100):
        '''
        根据X轴和Y轴坐标点击控件，默认点击控件正中间

        :Args:
         - x: X轴坐标，默认0
         - y: Y轴坐标，默认0
         - move_time: 移动到控件上之后的等待时间，默认100ms
        '''
        self.control.Click_2(x, y, move_time)

    def move_to(self, time=1000):
        '''
        移动到指定的控件上

        :Args:
         - time: 移动到控件上之后的等待时间，默认1000ms
        '''
        # self.control.MoveTo(time)#使用这个方式调用会报：TypeError: 'NoneType' object is not callable
        self.control.MoveTo

    def double_click(self):
        '''
        双击控件
        '''
        self.control.DoubleClick

    def set_find_element_time_out(self, time):
        '''
        设置查询元素的超时时间

        :Args:
         - time: 时间，单位毫秒
        '''
        self.control.SetFindElementTimeOut(time)

    def get_path(self):
        '''
        获取元素的当前的Rxpath路径

        :Returns:
            返回Rxpath路径
        '''
        return self.control.GetPath

    def get_attribute_value(self, name):
        '''
        根据属性的key值，获取对应的Value

        :Args:
         - name: 属性的key值

        :Returns:
            返回属性值
        '''
        return self.control.GetAttributeValue(name)

    def get_handle(self):
        '''
        获取元素对应的句柄

        :Returns:
            返回句柄
        '''
        return self.control.GetHandle

    def get_control_height(self):
        '''
        获取控件的高度

        :Returns:
            返回控件的高度
        '''
        return self.control.GetControlHeight

    def get_control_width(self):
        '''
        获取控件的宽度

        :Returns:
            返回控件的宽度
        '''
        return self.control.GetControlWidth

    def get_control_location_x(self):
        '''
        获取控件的相对坐标X

        :Returns:
            返回控件的相对坐标X
        '''
        return self.control.GetControlLocationX

    def get_control_location_y(self):
        '''
        获取控件的相对坐标Y

        :Returns:
            返回控件的相对坐标Y
        '''
        return self.control.GetControlLocationY

    def move_element_to(self, xdst, ydst, keep_time=0):
        '''
        将元素拖动到指定位置处

        :Args:
         - xdst: 目标位置的X轴坐标
         - ydst: 目标位置的Y轴坐标
         - time: 保持的时间，单位ms
        '''
        self.control.MoveElementTo(xdst, ydst, keep_time)

    def get_control_screen_location_x(self):
        '''
        获取控件的绝对坐标，屏幕坐标X轴

        :Returns:
            返回控件的绝对坐标，屏幕坐标X轴
        '''
        return self.control.GetControlScreenLocationX

    def get_control_screen_location_y(self):
        '''
        获取控件的绝对坐标，屏幕坐标Y轴

        :Returns:
            返回控件的绝对坐标，屏幕坐标Y轴
        '''
        return self.control.GetControlScreenLocationY

    def find_element(self, xpath, time=10000):
        '''
        根据相对路径查找元素，如果有多个控件符合，只返回第一个找到的控件

        :Args:
         - xpath: 相对此控件的Ranorex xpath
         - time: 查找时间，默认为10S，单位ms

        :Returns:
            返回控件的对象
        '''
        try:
            # self.get_memory()
            control = self.control.FindElement(xpath, time)
            return Control(control)
        except pywintypes.com_error as e:
            if 'Invalid RxPath' in str(e) or "Parsing RxPath" in str(e):
                message = "{}为无效的XPATH，请检查".format(xpath)
                raise CsClientError(CustomError.Error_EQ03001001_XPath_Invalid, extra_msg=message)
            elif 'No element found for path' in str(e):
                message = "在{}秒内没有找到XPath为{}的控件".format(time / 1000, xpath)
                raise CsClientError(CustomError.Error_EQ03001000_Cannot_Find_Element, extra_msg=message)
            else:
                # self.get_memory()
                raise CsClientError(str(e))

    def get_memory(self):
        mem = psutil.virtual_memory()
        total = float(mem.total) / 1024 / 1024 / 1024
        used = float(mem.used) / 1024 / 1024 / 1024
        kx = float(mem.free) / 1024 / 1024 / 1024
        percent = used / total

        str = "总内存为:{},已使用内存：{}，内存占用率为:{}".format(total, used, percent)
        logger.debug(str)

    def find_elements(self, xpath, time=10000):
        '''
        根据相对路径查找所有符合此Path的所有元素

        :Args:
         - xpath: 相对此控件的Ranorex xpath
         - time: 查找时间，默认为10S，单位ms

        :Returns:
            返回控件的对象集合
        '''
        try:
            control_list = self.control.FindElements(xpath, time)
            return [Control(control) for control in control_list]
        except pywintypes.com_error as e:
            if 'Invalid RxPath' in str(e) or "Parsing RxPath" in str(e):
                message = "{}为无效的XPATH，请检查".format(xpath)
                raise CsClientError(CustomError.Error_EQ03001001_XPath_Invalid, extra_msg=message)
            else:
                # self.get_memory()
                raise CsClientError(str(e))

    def is_element_exist(self, xpath, time=2000):
        '''
        判断某个控件是否存在

        :Args:
         - xpath: 相对此控件的Ranorex xpath
         - time: 查找时间，默认为2S，单位ms

        :Returns:
            True返回存在，反之则不存在
        '''
        try:
            bool_exist = self.control.IsElementExist(xpath, time)
            return bool_exist
        except pywintypes.com_error as e:
            if 'Invalid RxPath' in str(e) or "Parsing RxPath" in str(e):
                message = "{}为无效的XPATH，请检查".format(xpath)
                raise CsClientError(CustomError.Error_EQ03001001_XPath_Invalid, extra_msg=message)
            else:
                # self.get_memory()
                raise CsClientError(str(e))

    def find_all_children(self, time=10000):
        '''
        获取此控件的所有子控件以及孙子控件

        :Args:
         - time: 查找时间，默认为10S，单位ms

        :Returns:
            返回此控件的所有子控件以及孙子控件
        '''
        control_list = self.control.FindAllChildren(time)
        return [Control(control) for control in control_list]

    def press_keys(self, txt):
        '''
        用键盘事件输入文字或者{按键}

        :Args:
         - txt: 设置的文字或者{按键}
        '''
        self.control.PressKeys(txt)

    def send_keys(self, txt):
        '''
        用键盘事件输入文字

        :Args:
         - txt: 设置的文字
        '''
        self.control.SendKeys(txt)

    def send_keys_with_noclick(self, txt):
        '''
        用键盘事件输入文字，输入前不进行点击

        :Args:
         - txt: 设置的文字
        '''
        self.control.SendKeysWithNoClick(txt)

    def set_text_with_enter(self, txt, delay=0):
        '''
        设置的文字的时侯最后加上Enter键

        :Args:
         - txt: 设置的文字
         - delay: 输入文字到敲下enter的时延，单位为ms，默认0
        '''
        self.control.SetTextWithEnter(txt, delay)

    def focus(self):
        self.control.Focus

    def ensure_visible(self):
        return self.control.EnsureVisible

    def take_control_screenshot(self, save_path):
        '''
        截取控件的截图

        :Args:
         - save_path: 截图的保存路径
        '''
        self.control.GetControlScreenshot(save_path)

    def draw_line(self, original_x, original_y, destination_x, destination_y):
        '''
        在控件的指定位置画一条直线

        :Args:
         - original_x: 直线的起始x位置
         - original_y: 直线的起始y位置
         - destination_x: 直线的终点x位置
         - destination_y: 直线的终点y位置
        '''
        self.control.DrawLine(original_x, original_y, destination_x, destination_y)

    def drag_control(self, destination_x, destination_y):
        '''
        拖拽控件到某一位置

        :Args:
         - destination_x: 需要拖拽控件到的位置x
         - destination_y: 需要拖拽控件到的位置y
        '''
        self.control.DragControl(destination_x, destination_y)

    def find_element_by_attribute(self, by, value, assist='', timeout=10000):
        '''
        根据多属性查找当前元素节点下符合查找条件的元素集合，assist指的是辅助属性

        :Args:
         - by: 必须是'xpath'
         - value: ranorexpath
         - assist: 辅助属性，'属性名称:属性值'，支持多组属性，多组之间用逗号隔开
         - timeout: 查找元素的超时时间，在timeout时间内会不断的查找直到找到为止，超过timeout时间则不再继续查找

        :Returns:
            返回符合条件的元素，Control类型

        :Raises:
            CsClientError: 参数不正确

        :Example:
         - 例: ele_list = element.find_elements_by_attribute('xpath', '//input[@id="su"]', 'text:百度一下', 3)
        '''
        if by.upper() != 'XPATH' and by.upper() != 'RANOREXPATH':
            message = 'Ranorex只通过RanoreXpath访问元素！'
            raise CsClientError(CustomError.Error_EQ03001002_Find_Type_Invalid, extra_msg=message)
        if not assist:
            return self.find_element(value, timeout)
        else:
            element_list = self.find_elements_by_attribute(by, value, assist, timeout)
            if element_list:
                return element_list[0]
            else:
                message = by + ',' + value + ',' + assist + '属性对象没有找到!'
                raise CsClientError(CustomError.Error_EQ03001000_Cannot_Find_Element, extra_msg=message)

    def find_elements_by_attribute(self, by, value, assist='', timeout=10000):
        '''
        根据多属性查找当前元素节点下符合查找条件的元素集合，assist指的是辅助属性

        :Args:
         - by: 必须是'xpath'
         - value: ranorexpath
         - assist: 辅助属性，'属性名称:属性值'，支持多组属性，多组之间用逗号隔开
         - timeout: 查找元素的超时时间，在timeout时间内会不断的查找直到找到为止，超过timeout时间则不再继续查找

        :Returns:
            返回符合条件的元素集合，[Control]类型

        :Raises:
            CsClientError: 参数不正确

        :Example:
         - 例: ele_list = element.find_elements_by_attribute('xpath', '//input[@id="su"]', 'text:百度一下', 3)
        '''
        if by.upper() != 'XPATH' and by.upper() != 'RANOREXPATH':
            message = 'Ranorex只通过RanoreXpath访问元素！'
            raise CsClientError(CustomError.Error_EQ03001002_Find_Type_Invalid, extra_msg=message)
        if not assist:
            return self.find_elements(value, timeout)
        else:
            element_list = self.find_elements(value, timeout)
            if len(element_list) > 0:
                assist_list = assist.split(',')
                i = 0
                while i < len(element_list):
                    for e in assist_list:
                        index_number = e.index(':')
                        if e[0:index_number] == 'text':
                            temp = e[index_number + 1:]
                            # 因为用正则表达式进行文本匹配，文本可以含有正则表达式的特殊字符，所以把特殊字符进行转义
                            re_special_character = {'.': '\.'
                                , '*': '\*'
                                , '(': '\('
                                , ')': '\)'
                                , '$': '\$'
                                , '+': '\+'
                                , '[': '\['
                                , ']': '\]'
                                , '?': '\?'
                                , '{': '\{'
                                , '|': '\|'}
                            for key, value in re_special_character.items():
                                temp = temp.replace(key, value)
                            # temp = temp.replace(' ', '')
                            re_str = '^' + temp + '$'
                            if e[index_number + 1:].startswith('.'):
                                re_str = re_str.strip('^')
                            if e[index_number + 1:].endswith('.*'):
                                re_str = re_str.strip('$')
                            e_html_text = element_list[i].get_text()
                            e_html_text = "" if not e_html_text else e_html_text
                            # e_html_text = e_html_text.replace(" ","")
                            e_text = re.sub('<[^<>]*>', '', e_html_text).strip()
                            # e_text = re.sub('<[^<>]*>', '', e_html_text).strip()
                            if not e_text or not re.search(re_str, e_text):
                                element_list.remove(element_list[i])
                            else:
                                i += 1
                        elif e[0:index_number] == 'style':
                            re_str = e[index_number + 1:]
                            index_no = re_str.index(':')
                            element_style = element_list[i].get_style(re_str[0:index_no])
                            re_word = re_str[index_no + 1:].strip(';').strip()
                            if element_style == re_word:
                                i += 1
                            else:
                                element_list.remove(element_list[i])
                        else:
                            # 正则表达式中需要做设定，必须以给定的字符串打头和结尾
                            element_attr = element_list[i].get_attribute(e[0:index_number])
                            re_word = e[index_number + 1:]
                            if re_word not in element_attr.split(' '):
                                element_list.remove(element_list[i])
                            else:
                                i += 1
                return element_list
            else:
                return element_list

    def exist(self, by, value, assist='', timeout=10000):
        '''
        根据多属性查找当前元素节点下符合查找条件的元素集合，assist指的是辅助属性

        :Args:
         - by: 必须是'xpath'
         - value: ranorexpath
         - assist: 辅助属性，'属性名称:属性值'，支持多组属性，多组之间用逗号隔开
         - timeout: 查找元素的超时时间，在timeout时间内会不断的查找直到找到为止，超过timeout时间则不再继续查找

        :Returns:
            存在返回符合条件的元素，不存在返回None

        :Raises:
            CsClientError: 参数不正确

        :Example:
         - 例: ele = element.exist('xpath', '//input[@id="su"]', 'text:百度一下', 3)
        '''
        if by.upper() != 'XPATH':
            message = 'Ranorex只通过RanoreXpath访问元素！'
            raise CsClientError(CustomError.Error_EQ03001002_Find_Type_Invalid, extra_msg=message)
        try:
            if not assist:
                if self.is_element_exist(value, timeout):
                    return self.find_element(value, timeout)
                else:
                    return None
            else:
                element = self.find_element_by_attribute(by, value, assist, timeout)
                return element
        except CsClientError:
            return None

    def find_first_visible_element(self, by, value, timeout=10000):
        '''
        返回符合条件的第一个可见的元素

        :Args:
         - by: 是'xpath'或者ranorexpath
         - value: ranorexpath
         - timeout: 查找元素的超时时间，在timeout时间内会不断的查找直到找到为止，超过timeout时间则不再继续查找

        :Returns:
            返回符合条件的元素，Control类型

        :Raises:
            CsClientError: 参数不正确

        :Example:
         - 例: ele_list = element.find_elements_by_attribute('xpath', '//input[@id="su"]', 'text:百度一下', 3)
        '''
        if by.upper() != 'XPATH' and by.upper() != 'RANOREXPATH':
            message = 'Ranorex只通过RanoreXpath访问元素！'
            raise CsClientError(CustomError.Error_EQ03001002_Find_Type_Invalid, extra_msg=message)

        elements = self.find_elements(value, time=timeout)
        for element in elements:
            if element.is_visible():
                return element
            else:
                element.ensure_visible()
                if element.is_visible():
                    return element

        if len(elements)>0:
        # 没有可见的控件时，则返回找到的第一个控件
            return elements[0]

        return None
